﻿#region Using Namespaces
using System;
using System.Text;
using Microsoft.Xna.Framework;
#endregion

namespace GeoClipmapping.Components
{
    public class FPScounter : GameComponent, IFormattable
    {
        #region Variables
        // Frames per second variables
        private long totalFrames;
        private double currentFPS;
        private double averageFPS;
        private double highestFPS;
        private double lowestFPS;

        private double fpsTimer;    // milisecond counter
        private double fpsCounter;  // frames per second counter

        // Time per frame variables
        private double lastFrameTime;
        private double averageFrameTime;
        private double bestFrameTime;
        private double worstFrameTime;

        private double resetTimeMS;
        private double resetTimeS;

        private GameTime gameTime;
        private bool useRealtime;

        #endregion

        #region Properties
        public long TotalFrames
        {
            get { return totalFrames; }
        }

        public double FPScurrent
        {
            get { return currentFPS; }
        }

        public double FPSaverage
        {
            get { return averageFPS; }
        }

        public double FPShighest
        {
            get { return highestFPS; }
        }

        public double FPSlowest
        {
            get { return lowestFPS; }
        }

        public double FrameTimeLast
        {
            get { return lastFrameTime; }
        }

        public double FrameTimeAverage
        {
            get { return averageFrameTime; }
        }

        public double FrameTimeBest
        {
            get { return bestFrameTime; }
        }

        public double FrameTimeWorst
        {
            get { return worstFrameTime; }
        }

        public bool UseRealtime
        {
            get { return useRealtime; }
            set { useRealtime = value; }
        }
        #endregion

        #region Initializing
        /// <summary>
        /// Creates a new FPS counter.
        /// </summary>
        /// <param name="game"></param>
        public FPScounter(Game game)
            : base(game)
        {
            gameTime = new GameTime();
        }

        /// <summary>
        /// Creates a new FPS counter.
        /// </summary>
        /// <param name="game"></param>
        public FPScounter(Game game, bool useRealtime)
            : base(game)
        {
            gameTime = new GameTime();
            this.useRealtime = useRealtime;
        }
        /// <summary>
        /// Initializes all variables of the FPS counter.
        /// </summary>
        public override void Initialize()
        {
            totalFrames = 0;
            averageFPS = 0;
            highestFPS = 0;
            lowestFPS = 999;
            averageFrameTime = 0;
            bestFrameTime = 999;
            worstFrameTime = 0;
            currentFPS = 1;

            resetTimeMS = 0;
            resetTimeS = 0;

            base.Initialize();
        }
        #endregion

        #region Update
        private TimeSpan lastframeElapsedTime;
        private TimeSpan lastframeTotalTime;
        /// <summary>
        /// Updates component. Counts frames and time.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            this.gameTime = gameTime;

            if (useRealtime)
            {
                lastframeElapsedTime = gameTime.ElapsedRealTime;
                lastframeTotalTime = gameTime.TotalRealTime;
                fpsTimer += gameTime.ElapsedRealTime.TotalMilliseconds;
            }
            else
            {
                lastframeElapsedTime = gameTime.ElapsedGameTime;
                lastframeTotalTime = gameTime.TotalGameTime;
                fpsTimer += gameTime.ElapsedGameTime.TotalMilliseconds;
            }

            totalFrames++;
            fpsCounter++;

            if (fpsTimer >= 1000)
            {
                currentFPS = fpsCounter;
                lastFrameTime = fpsTimer / currentFPS;

                fpsTimer = 0;
                fpsCounter = 0;
            }

            averageFPS = totalFrames / (lastframeTotalTime.TotalSeconds - resetTimeS);

            if (currentFPS > highestFPS && !double.IsInfinity(currentFPS))
            {
                highestFPS = currentFPS;
            }
            else if (currentFPS < lowestFPS)
            {
                lowestFPS = currentFPS;
            }


            averageFrameTime = (lastframeTotalTime.TotalMilliseconds - resetTimeMS) / totalFrames;
            if (lastFrameTime < bestFrameTime && lastFrameTime != 0)
            {
                bestFrameTime = lastFrameTime;
            }
            else if (lastFrameTime > worstFrameTime)
            {
                worstFrameTime = lastFrameTime;
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Resets the counter.
        /// </summary>
        public void Reset()
        {
            totalFrames = 0;
            averageFPS = 0;
            highestFPS = 0;
            lowestFPS = 999;
            averageFrameTime = 0;
            bestFrameTime = 999;
            worstFrameTime = 0;

            resetTimeMS += (gameTime.TotalGameTime.TotalMilliseconds - resetTimeMS);
            resetTimeS += (gameTime.TotalGameTime.TotalSeconds - resetTimeS);
        }

        /// <summary>
        /// Returns every FPS information in a string.
        /// </summary>
        /// <returns>FPS inormation</returns>
        public override string ToString()
        {
            return string.Format("Fps {0:F1}", currentFPS);
        }
        #endregion

        #region IFormattable Member
        /// <summary>
        /// supported formats:
        /// fa - average fps
        /// fc - current fps
        /// fh - highest fps
        /// fl - lowest fps
        /// ta - average frametime
        /// tc - current frametime
        /// th - highest frametime
        /// tl - lowest frametime
        /// fps-stats - all fps statistics
        /// </summary>
        /// <param name="format"></param>
        /// <param name="formatProvider"></param>
        /// <returns></returns>
        public string ToString(string format, IFormatProvider formatProvider)
        {
            if (formatProvider != null)
            {
                ICustomFormatter fmt = formatProvider.GetFormat(this.GetType()) as ICustomFormatter;
                if (fmt != null)
                {
                    return fmt.Format(format, this, formatProvider);
                }
            }

            switch (format)
            {
                case "fa":
                    return string.Format("FPS average {0:F1}", averageFPS);
                case "fc":
                    return string.Format("FPS current {0:F1}", currentFPS);
                case "fh":
                    return string.Format("FPS highest {0:F1}", highestFPS);
                case "fl":
                    return string.Format("FPS lowest {0:F1}", lowestFPS);

                case "ta":
                    return string.Format("Frametime average {0:F1}ms", averageFrameTime);
                case "tc":
                    return string.Format("Frametime current {0:F1}ms", lastFrameTime);
                case "th":
                    return string.Format("Frametime highest {0:F1}ms", worstFrameTime);
                case "tl":
                    return string.Format("Frametime lowest {0:F1}ms", bestFrameTime);
                case "fps-stats":
                    StringBuilder b = new StringBuilder();
                    b.AppendLine(string.Format("Frames total {0}", totalFrames));
                    b.AppendLine(string.Format("FPS average {0:F1}", averageFPS));
                    b.AppendLine(string.Format("FPS current {0:F1}", currentFPS));
                    b.AppendLine(string.Format("FPS highest {0:F1}", highestFPS));
                    b.AppendLine(string.Format("FPS lowest  {0:F1}", lowestFPS));

                    b.AppendLine(string.Format("Frametime average {0:F1}ms", averageFrameTime));
                    b.AppendLine(string.Format("Frametime current {0:F1}ms", lastFrameTime));
                    b.AppendLine(string.Format("Frametime highest {0:F1}ms", worstFrameTime));
                    b.AppendLine(string.Format("Frametime lowest  {0:F1}ms", bestFrameTime));
                    b.AppendLine(string.Format("Total Gametime {0}", gameTime.TotalGameTime));
                    b.AppendLine(string.Format("Total Realtime {0}", gameTime.TotalRealTime));
                    return b.ToString();

                case "n":
                case "G":
                default:
                    return ToString();
            }
        }

        #endregion
    }
}